package com.pvdnc.riru.appinjector;

import android.annotation.SuppressLint;
import android.app.Application;
import android.util.ArrayMap;
import android.util.Log;

import com.pvdnc.riru.appinjector.nuwa.Nuwa;
import com.pvdnc.riru.appinjector.nuwa.mirror.R_ApplicationLoaders;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import andhook.lib.xposed.XC_MethodHook;
import andhook.lib.xposed.XposedBridge;

@SuppressLint("PrivateApi")
public class AppClassLoader {
    private static final String TAG=AppClassLoader.class.getSimpleName();

    public static final AppClassLoader INSTANCE=new AppClassLoader();

    private final ClassLoader mRiruClassLoader;

    private AppClassLoader(){
        mRiruClassLoader=getClass().getClassLoader();
    }

    public void hookAll(){
        try {
            hookGetAppClassLoader();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private Object mApplicationLoaders;
    private ClassLoader mAppClassLoader;

    private final Map<ClassLoader,String> mLoaderZipMap=new ConcurrentHashMap<>();

    private void hookGetAppClassLoader() throws Exception {
        final Class<?> alClass=Class.forName("android.app.ApplicationLoaders");
        //public ClassLoader getClassLoader(String zip, String libPath, ClassLoader parent)
        Method method=alClass.getDeclaredMethod("getClassLoader",
                String.class,String.class,ClassLoader.class);
        XposedBridge.hookMethod(method,new XC_MethodHook(){
            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                super.afterHookedMethod(param);
                try {
                    mApplicationLoaders=param.thisObject;

                    String zip = (String) param.args[0];
                    ClassLoader cl = (ClassLoader) param.getResult();
                    Log.d(TAG,"old app classloader:\n\t"+cl);


                    //缓存类加载器与zip的对应关系
                    mLoaderZipMap.put(cl,zip);

                    //设置结果
                    param.setResult(cl);
                    mAppClassLoader=cl;
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        });
        Log.d(TAG,"finish hooking getClassLoader");
    }

    public void injectRiruClasses(ClassLoader cl) throws Exception {
        Nuwa.injectDexAtFirst(cl, mRiruClassLoader);
        //更新缓存
        String zip=mLoaderZipMap.get(cl);
        addCache(mApplicationLoaders, zip,cl);
        Log.d(TAG, "zip:" + zip + " -> loader:\n\t" + cl);
    }

    public void injectRiruClasses() throws Exception {
        injectRiruClasses(mAppClassLoader);
    }

    private static void addCache(Object thisObject, String zip, ClassLoader cl){
        ArrayMap<String, ClassLoader> loadersMap= R_ApplicationLoaders.mLoaders.get(thisObject);
        loadersMap.put(zip,cl);
        R_ApplicationLoaders.mLoaders.set(thisObject,loadersMap);
    }

    private static void removeCache(Object thisObject,String zip){
        ArrayMap<String, ClassLoader> loadersMap= R_ApplicationLoaders.mLoaders.get(thisObject);
        loadersMap.remove(zip);
        R_ApplicationLoaders.mLoaders.set(thisObject,loadersMap);
    }

    public void removeRiruClasses(){
        if(mAppClassLoader==null)
            return;
        removeRiruClasses(mAppClassLoader);
    }

    public void removeRiruClasses(ClassLoader appClassLoader){
        try {
            Nuwa.removeDex(appClassLoader,mRiruClassLoader);
            String zip=mLoaderZipMap.get(appClassLoader);
            removeCache(mApplicationLoaders,zip);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
